﻿
using Boilen.Primitives.Implementers;
using Boilen.Primitives.Members;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;


namespace Boilen.Primitives {

    /// <summary>
    /// Represents a partial definition of a type.
    /// </summary>
    public sealed partial class PartialType {

        private readonly Dictionary<InsertionPoint, string> insertedText_ = new Dictionary<InsertionPoint, string>( );
        private readonly List<AttributeMember> cctorAttributes_ = new List<AttributeMember>( );
        private readonly List<AttributeMember> ctorAttributes_ = new List<AttributeMember>( );
        private readonly List<IImplementer> implementers_ = new List<IImplementer>( );
        private readonly TypeRepository typeRepository_ = new TypeRepository( );
        private readonly string kind_;

        private readonly Type type_;
        private readonly string typeName_;
        private readonly string typeFullName_;
        private readonly string typeNamespace_;
        private readonly string simpleTypeName_;
        private readonly string docTypeName_;
        private readonly bool useFullNamePrefix_;

        private readonly bool isDerivedType_;
        private readonly Type baseType_;
        private readonly string baseTypeName_;
        private readonly string silverlightBaseTypeName_;


        /// <summary>
        /// Gets the repository of types referenced by this 
        /// </summary>
        public TypeRepository TypeRepository { get { return this.typeRepository_; } }

        /// <summary>
        /// Gets the type to generate code for.
        /// </summary>
        public Type Type { get { return this.type_; } }

        /// <summary>
        /// Gets the name of the generator's type.
        /// </summary>
        public string TypeName { get { return this.typeName_; } }

        /// <summary>
        /// Gets the full name of the generator's type.
        /// </summary>
        public string TypeFullName { get { return this.typeFullName_; } }

        /// <summary>
        /// Gets a string that can be used to prefix members of the generator's type.
        /// </summary>
        public string TypeNamePrefix { get { return this.useFullNamePrefix_ ? this.TypeFullName : this.TypeName; } }

        /// <summary>
        /// Gets the simple name of the generator's type.
        /// </summary>
        public string SimpleTypeName { get { return this.simpleTypeName_; } }

        /// <summary>
        /// Gets the name of the generator's type that can be used in documentation references.
        /// </summary>
        public string DocTypeName { get { return this.docTypeName_; } }

        /// <summary>
        /// Gets the namespace of the generator's type.
        /// </summary>
        public string Namespace { get { return this.typeNamespace_; } }

        /// <summary>
        /// Gets a value indicating whether the type is in an internal namespace.
        /// </summary>
        public bool IsInternal { get { return this.typeNamespace_.EndsWith( ".Internal" ); } }

        /// <summary>
        /// Gets the kind of the generator's type (either "class" or "struct").
        /// </summary>
        public string Kind { get { return this.kind_; } }

        /// <summary>
        /// Gets a value indicating whether the type is derived from an explicit base type.
        /// </summary>
        public bool IsDerivedType { get { return this.isDerivedType_; } }

        /// <summary>
        /// Gets the type to declare as the generator's base type.
        /// </summary>
        public Type BaseType { get { return this.baseType_; } }

        /// <summary>
        /// Gets the name of the type to declare as the generator's base type.
        /// </summary>
        public string BaseTypeName { get { return this.baseTypeName_; } }

        /// <summary>
        /// Gets the name of the type to declare as the generator's base type in silverlight.
        /// </summary>
        public string SilverlightBaseTypeName { get { return this.silverlightBaseTypeName_; } }

        /// <summary>
        /// Gets or sets whether the <see cref="FrameworkElement.DefaultStyleKey"/> property can be overridden on WPF types.
        /// </summary>
        public bool IsStyleable { get { return TestType( typeof( FrameworkElement ) ) || TestType( typeof( FrameworkContentElement ) ); } }

        /// <summary>
        /// Gets or sets whether the type derives from <see cref="Freezable"/>.
        /// </summary>
        public bool IsFreezable { get { return TestType( typeof( Freezable ) ); } }

        /// <summary>
        /// Gets a value indicating whether the type is sealed.
        /// </summary>
        public bool IsSealed { get; set; }

        /// <summary>
        /// Gets or sets the accessibility of any instance constructors.
        /// </summary>
        public Accessibility ConstructorAccessibility { get; set; }

        /// <summary>
        /// Gets or sets whether to override the <see cref="FrameworkElement.DefaultStyleKey"/> property on WPF types.
        /// </summary>
        public bool OverrideDefaultStyleKey { get; set; }


        /// <summary>
        /// Gets the list of implementers that will be generated.
        /// </summary>
        public IList<IImplementer> Implementers { get { return this.implementers_; } }


        private PartialType( string derivedTypeName, Type type, Type baseType, string silverlightBaseTypeName ) {
            Ensure.NotNull( type );
            Ensure.Satisfies( type.IsClass || type.IsValueType, "Can only generate code for class and struct types." );

            this.type_ = type;
            this.kind_ = type.IsClass ? "class" : "struct";
            this.isDerivedType_ = baseType != null;

            if( !string.IsNullOrEmpty( derivedTypeName ) ) {
                this.typeName_ = derivedTypeName;
                int index = derivedTypeName.IndexOf( '<' );
                if( index > 0 )
                    derivedTypeName = derivedTypeName.Substring( 0, index );
                this.simpleTypeName_ = derivedTypeName;
            }
            else {
                this.typeName_ = this.TypeRepository.GetTypeName( type );
                this.simpleTypeName_ = this.TypeRepository.GetSimpleTypeName( type );
            }
            this.typeNamespace_ = this.TypeRepository.GetTypeNamespace( type );
            this.typeFullName_ = this.typeNamespace_ + "." + this.typeName_;
            this.docTypeName_ = Doc.GetDocTypeName( this.typeName_ );

            this.baseType_ = baseType ?? type.BaseType;
            this.baseTypeName_ = this.TypeRepository.GetTypeName( this.baseType_ );
            this.silverlightBaseTypeName_ = silverlightBaseTypeName
                ?? (this.baseType_ == typeof( Freezable ) ? this.TypeRepository.GetTypeName( typeof( DependencyObject ) ) : null)
                ?? (this.baseType_ == typeof( FrameworkContentElement ) ? this.TypeRepository.GetTypeName( typeof( FrameworkElement ) ) : null)
                ;

            this.IsSealed = this.Type.IsSealed;
            this.ConstructorAccessibility = this.Type.IsAbstract ? Accessibility.Protected : Accessibility.Public;
            this.OverrideDefaultStyleKey = !this.Type.IsAbstract && !typeof( Panel ).IsAssignableFrom( this.Type );
            this.useFullNamePrefix_ = this.Type.GetProperty( this.TypeName ) != null;

            if( this.IsFreezable ) {
                bool baseImplementsIFreezable = this.BaseType != typeof( Freezable );
                this.Implementers.Add( new FreezableImplementer( this ) { DeclareMembers = !baseImplementsIFreezable } );
            }
        }

        /// <summary>
        /// Initializes a new <see cref="PartialType"/> instance.
        /// </summary>
        /// <param name="type">The type to generate code for.</param>
        public PartialType( Type type )
            : this( null, type, null, null ) { }

        /// <summary>
        /// Initializes a new <see cref="PartialType"/> instance.
        /// </summary>
        /// <param name="name">The name of the derived type to create.</param>
        /// <param name="baseType">The type to declare as the base class of <paramref name="baseType"/>.</param>
        public PartialType( string name, Type baseType )
            : this( name, baseType, baseType, null ) {
            Ensure.NotNull( name );
        }

        /// <summary>
        /// Initializes a new <see cref="PartialType"/> instance.
        /// </summary>
        /// <param name="type">The type to generate code for.</param>
        /// <param name="baseType">The type to declare as the base class of <paramref name="type"/>.</param>
        public PartialType( Type type, Type baseType )
            : this( null, type, baseType, null ) {
            Ensure.NotNull( baseType );
            Ensure.ArgSatisfies( type != baseType, "baseType", "Cannot use {0} as its own base type.", type );
        }

        /// <summary>
        /// Initializes a new <see cref="PartialType"/> instance.
        /// </summary>
        /// <param name="type">The type to generate code for.</param>
        /// <param name="baseType">The type to declare as the base class of <paramref name="type"/>.</param>
        /// <param name="silverlightBaseTypeName">The full name of the type to declare as the base type in silverlight, instead of <paramref name="baseType"/>.</param>
        public PartialType( Type type, Type baseType, string silverlightBaseTypeName )
            : this( null, type, baseType, silverlightBaseTypeName ) {
            Ensure.ArgSatisfies( type != baseType, "baseType", "Cannot use {0} as its own base type.", type );
            Ensure.NotNull( baseType, silverlightBaseTypeName );
        }


        private void AddSubNamespace( string subNamespace ) {
            if( string.IsNullOrEmpty( subNamespace ) )
                return;

            string rootNamespace = this.typeNamespace_.Split( '.' ).First( );
            this.TypeRepository.AddNamespace( rootNamespace + "." + subNamespace );
        }

        private bool TestType( Type targetType ) {
            bool isTargetType = targetType.IsAssignableFrom( this.BaseType );
            if( isTargetType )
                TypeRepository.AddNamespace( targetType );
            return isTargetType;
        }

        private IList<Member> GetMembers( ) {
            var members = new List<Member>( );

            if( this.OverrideDefaultStyleKey && this.IsStyleable ) {
                Type styleableType = TestType( typeof( FrameworkElement ) ) ? typeof( FrameworkElement ) : typeof( FrameworkContentElement );
                var defaultStyleKeyDependencyProperty = (DependencyProperty)styleableType
                    .GetField( "DefaultStyleKeyProperty", BindingFlags.Static | BindingFlags.NonPublic )
                    .GetValue( null );

                var defaultStyleKeyOverride = new MetadataOverride<object>( this, defaultStyleKeyDependencyProperty, styleableType )
                    .SetOverrideInSilverlight( TestType( typeof( Control ) ) )
                    .SetDefaultValue( this.Type );

                defaultStyleKeyOverride.Prepare( );
                members.AddRange( defaultStyleKeyOverride.Members );
            }

            members.AddRange( this.Implementers.SelectMany( i => i.Members ) );

            return members;
        }

    }

}
